#include "MarkerEntity.h"
#include "core/CMath.h"
#include "GLTFParser.h"
#include "Cache.h"
#include "GeoUtil.h"
#include "Global.h"

#define CHECK_ZHICHE_INFO_AVAILABILITY if (!GLTFParser::zhicheInfo().isValid()) return;

void MarkerEntity::create(MarkerType type) {
	switch (type) {
		case MarkerType::ALC: createALC(RenderOrder::Third); break;
		case MarkerType::SQ_ALC: createSQ_ALC(RenderOrder::Third); break;
		case MarkerType::LDW: createLDW(RenderOrder::Third); break;
		case MarkerType::DCLC: createDCLC(RenderOrder::Fourth); break;
		case MarkerType::ACC_TG: createACC_TG(RenderOrder::Sixth); break;
		case MarkerType::RoadBg: createRoadBg(RenderOrder::Opaque); break;
		case MarkerType::TailLamp: createTailLamp(RenderOrder::AfterZhiche); break;
		case MarkerType::ZhicheCircle: createBreathCircle(RenderOrder::Second); break;
	}
}

void MarkerEntity::create(MarkerType type, int boundModelID) {
	if (MarkerType::ACC_FD == type)
		createACC_FD(RenderOrder::Fifth, boundModelID);
	else if (MarkerType::ACC_Circle == type) {
		const auto &model = Cache::modelArg(boundModelID);
		createBreathCircle(RenderOrder::Second, model.pos);
	}
}

void MarkerEntity::createALC(RenderOrder order) {
	m_renderOrder = order;
	auto alc = Cache::status().alc;
	CColor colorAlcL, colorAlcR;
	switch (alc) {
		case ALC::INVALID: return;// no ALC
		case ALC::Left_Normal:
			colorAlcL = Config::Marker_Ellipse_ColorNormal;
			break;
		case ALC::Left_Warning:
			colorAlcL = Config::Marker_Ellipse_ColorWarning;
			break;
		case ALC::Right_Normal:
			colorAlcR = Config::Marker_Ellipse_ColorNormal;
			break;
		case ALC::Right_Warning:
			colorAlcR = Config::Marker_Ellipse_ColorWarning;
			break;
	}

	const auto& EqL = Cache::lineArg(LineTag::L).eq;
	const auto& EqR = Cache::lineArg(LineTag::R).eq;
	if (colorAlcL.isValid() && EqL.isValid())
		createLineEllipse(EqL, colorAlcL, HDir::LEFT);
	else if (colorAlcR.isValid() && EqR.isValid())
		createLineEllipse(EqR, colorAlcR, HDir::RIGHT);
}

void MarkerEntity::createLDW(RenderOrder order) {
	m_renderOrder = order;
	LDW ldw = Cache::status().ldw;
	CColor colorLdwL, colorLdwR;
	switch (ldw) {
		case LDW::From_Left_Warning:
			colorLdwL = Config::Marker_Ellipse_ColorWarning;
			break;
		case LDW::From_Right_Warning:
			colorLdwR = Config::Marker_Ellipse_ColorWarning;
			break;
	}

	auto& eqL = Cache::lineArg(LineTag::L).eq;
	auto& eqR = Cache::lineArg(LineTag::R).eq;
	if (colorLdwL.isValid() && eqL.isValid())
		createLineEllipse(eqL, colorLdwL, HDir::RIGHT);
	else if (colorLdwR.isValid() && eqR.isValid())
		createLineEllipse(eqR, colorLdwR, HDir::LEFT);
}

void MarkerEntity::createDCLC(RenderOrder order) {
	m_renderOrder = order;
	CHECK_ZHICHE_INFO_AVAILABILITY
	DCLC dclc = Cache::status().dclc;
	LineEq eqL, eqR;
	if (DCLC::ToLeft == dclc) {
		eqL = Cache::lineArg(LineTag::L2).eq;
		eqR = Cache::lineArg(LineTag::L).eq;
	} else if (DCLC::ToRight == dclc) {
		eqL = Cache::lineArg(LineTag::R).eq;
		eqR = Cache::lineArg(LineTag::R2).eq;
	} else if (DCLC::Mid == dclc) {
		eqL = Cache::lineArg(LineTag::L).eq;
		eqR = Cache::lineArg(LineTag::R).eq;
	}
	if (!eqL.isValid() || !eqR.isValid())
		return;
	const float TopZ = DataFilter::get()->skylineZ() + Config::Marker_DclcArrow_ZEndBelowSkyline;
	CVector<P> vertices;
	bool isValid = GeoUtil::createLineArrow(vertices, eqL, eqR, TopZ, GLTFParser::zhicheInfo().width, DCLC::Mid != dclc);
	if (!isValid)
		return;

	addUniformStruct("arrow",
		Field("zStart", Config::Marker_DclcArrow_ZStart),
		Field("zEnd", TopZ)
	);
	
	auto e = install(Config::Shader_DclcArrow, PrimitiveType::TRIANGLE_STRIP, vertices, Config::Y_DclcArrow, Config::Marker_DclcArrow_Color);
	if (!e) return;

	CVector<float> lVerticesX, rVerticesX;
	for (int i = 0; i < vertices.size(); i++) {
		auto& x = vertices[i].x;
		if (i % 2 == 0)
			lVerticesX << x << x;
		else
			rVerticesX << x << x;
	}

	if (Proj::isKT() || Proj::isSQZK()) {
		addUniformStruct("arrow", Field("edgeW", Config::Marker_DclcArrow_EdgeW));

		installExtraVABO("posLx", lVerticesX);
		installExtraVABO("posRx", rVerticesX);
	}
}

void MarkerEntity::createACC_TG(RenderOrder order) {
	m_renderOrder = order;
	CHECK_ZHICHE_INFO_AVAILABILITY
	ACC_TG accTG = Cache::status().accTG;
	CVector<P> vertices;
	CVector<UInt> indices;
	const CColor CColor = Config::Marker_AccTG_Color;
	int current = static_cast<int>(accTG);
	int First = static_cast<int>(ACC_TG::Gear_First);
	int Second = static_cast<int>(ACC_TG::Gear_Second);
	int Third = static_cast<int>(ACC_TG::Gear_Third);
	int Fourth = static_cast<int>(ACC_TG::Gear_Fourth);
	if (current >= First)
		createAccTGRects(0, vertices, indices);
	if (current >= Second)
		createAccTGRects(1, vertices, indices);
	if (current >= Third)
		createAccTGRects(2, vertices, indices);
	if (current >= Fourth)
		createAccTGRects(3, vertices, indices);

	install(Config::Shader_AccTG, PrimitiveType::TRIANGLES, vertices, Config::Y_AccTG, indices, CColor);
}

void MarkerEntity::createTailLamp(RenderOrder order) {
	m_renderOrder = order;
	TailLamp tailLamp = Cache::status().tailLamp;
	if (TailLamp::Brake_On == tailLamp) {
		const CColor& color = Config::Marker_Lamp_BrakeColor;
		createTailLampLight(color, HDir::LEFT, ASCHILD);
		createTailLampLight(color, HDir::RIGHT, ASCHILD);
		return;
	}
	const CColor& color = Config::Marker_Lamp_TurnColor;
	if (TailLamp::Turn_Left == tailLamp)
		createTailLampLight(color, HDir::LEFT);
	else if (TailLamp::Turn_Right == tailLamp)
		createTailLampLight(color, HDir::RIGHT);
	else if (TailLamp::Turn_Both == tailLamp) {
		createTailLampLight(color, HDir::LEFT, ASCHILD);
		createTailLampLight(color, HDir::RIGHT, ASCHILD);
	}
}

void MarkerEntity::createACC_FD(RenderOrder order, int boundModelID) {
	m_renderOrder = order;
	CVector<P> vertices;
	const auto& model = Cache::modelArg(boundModelID);
	const auto& color = model.color;
	const auto& info = GLTFParser::info(model.type);
	const float W = info.width * Config::Marker_AccFD_TopWByFrontWTimes;
	const float BW = info.width * Config::Marker_AccFD_BottomWByFrontWTimes;
	const float H = info.width * Config::Marker_AccFD_HByFrontWTimes;
	const float D = info.maxZ + Config::Marker_AccFD_FrontOffsetD;
	auto TC = GeoUtil::getPointOnVector(model.pos, model.angle + C_PI/2, D, false);// top center point
	auto BC = P(TC.x, TC.z + H);// bottom center point
	auto topLeft = P(TC.x - W / 2, TC.z);
	auto topRight = P(TC.x + W / 2, TC.z);
	auto bottomLeft = P(TC.x - BW / 2, TC.z + H);
	auto bottomRight = P(TC.x + BW / 2, TC.z + H);
	vertices << topLeft << topRight << bottomLeft << bottomRight;

	setRotationY(model.angle, Vec3(TC.x, Config::Y_AccFD, TC.z));

	addUniformStruct("trapezoid",
		Field("topCenter", TC.toVec3(Config::Y_AccFD)),
		Field("bottomCenter", BC.toVec3(Config::Y_AccFD))
	);

	install(Config::Shader_AccFD, PrimitiveType::TRIANGLE_STRIP, vertices, Config::Y_AccFD, color);
}

void MarkerEntity::createRoadBg(RenderOrder order) {
	m_renderOrder = order;
	if (Cache::validLineNum() < 2)
		return;

	auto lineArgs = Cache::lineArgMap().values();
	std::sort(lineArgs.begin(), lineArgs.end(), [](const LineArg& a, const LineArg& b) { return a.eq.C[0] > b.eq.C[0]; });
	CVector<LineEq> sortedEqs;
	for (auto& lineArg : lineArgs)
		sortedEqs << lineArg.eq;

	const auto& Style = Config::Marker_RoadBg_Style;
	const auto& Color = Global::isDarkMode() ? Config::Marker_RoadBg_DarkColor : Config::Marker_RoadBg_LightColor;
	if (RoadBgStyle::DotMatrix == Style) {
		CVector<P> instancedVertices;
		GeoUtil::createRoadBg(instancedVertices, sortedEqs, Style);
		if (instancedVertices.empty())
			return;
		setInstancedVertices(instancedVertices, Config::Y_RoadBg);

		CVector<P> vertices;
		GeoUtil::createCircle(vertices, P(0, 0), Config::Marker_RoadBg_DotRadius, 8);

		install(Config::Shader_RoadDotBg, PrimitiveType::TRIANGLE_FAN, vertices, Config::Y_RoadBg, Color);
	} else if (RoadBgStyle::Fill == Style) {
		CVector<P> allVertices;
		GeoUtil::createRoadBg(allVertices, sortedEqs, Style);
		auto entity = install(Config::Shader_Line, PrimitiveType::TRIANGLE_STRIP, allVertices, Config::Y_RoadBg, Color);
		if (entity) {
			auto fadeEndZ = allVertices.back().z;// note: if allVertices is empty, error throws
			entity->setFadeOutEndZ(fadeEndZ, Config::Line_FadeByEndZOffsetPx);
		}
	}
}

void MarkerEntity::createBreathCircle(RenderOrder order, const P& modelPos) {
	m_renderOrder = order;
	P center;
	float maxRadius = Config::Marker_ActiveCircle_Radius;
	if (modelPos.isValid()) {
		center = modelPos;

		const auto& scalerFunc = DataFilter::get()->get2DScalerFunc();
		auto scale = scalerFunc(modelPos.z);

		maxRadius *= scale;
	} else {
		const auto &zhicheInfo = GLTFParser::zhicheInfo();
		if (!zhicheInfo.isValid())
			return;
		center = P(0, zhicheInfo.maxZ - zhicheInfo.length / 2);
	}

	const float MinRadius = maxRadius * 0.6;
	const int BreathFrameGap = Config::Marker_ActiveCircle_BreathFrameGap;
	constexpr int PointsDensity = 50;

	// keep breathing as a continuous process of change
	// avoid discontinuity between this state and the previous state, example: frameCnt 8->10 not 8->9, jump 1 frame
	static int LastFrameCnt = Global::frameCnt();
	static int DeltaFrameCnt = 0;

	int frameCnt = Global::frameCnt();
	DeltaFrameCnt += frameCnt - LastFrameCnt - 1;
	LastFrameCnt = frameCnt;
	frameCnt -= DeltaFrameCnt;

	int n = frameCnt % BreathFrameGap;// 0 ~ BreathFrameGap-1
	float progress = float(n) / (BreathFrameGap - 1);// 0~1
	float radius = 0;

	static bool IsExpanding = true;
	if (IsExpanding)
		radius = CMath::mix(MinRadius, maxRadius, progress);
	else
		radius = CMath::mix(MinRadius, maxRadius, 1 - progress);
	
	if (progress == 1) IsExpanding = !IsExpanding;

	CVector<P> vertices;
	GeoUtil::createCircle(vertices, center, radius, PointsDensity);

	addUniformStruct("circle",
		Field("center", center),
		Field("radius", radius)
	);

	install(Config::Shader_ActiveCircle, PrimitiveType::TRIANGLE_FAN, vertices, Config::Y_ActiveCircle, "#269CFF");
}

void MarkerEntity::createAccTGRects(int i, CVector<P>& vertices, CVector<UInt>& indices) {
	const auto& ZhicheInfo = GLTFParser::zhicheInfo();
	const float ZhicheW = ZhicheInfo.width;
	const auto ZhicheFrontZ = -(ZhicheInfo.length - ZhicheInfo.maxZ);
	const float OffsetD = std::abs(ZhicheFrontZ) + Config::Marker_ACCTG_ZhicheHeadOffsetD;
	const float W = ZhicheW * Config::Marker_AccTG_WByZhicheWTimes;
	const float RectH = ZhicheW * Config::Marker_AccTG_RectHByZhicheWTimes;
	const float Interval = ZhicheW * Config::Marker_AccTG_RectIntervalByZhicheWTimes;

	const auto& bottomL = P(-W/2, -OffsetD - i * (RectH + Interval));
	const auto& bottomR = P(-bottomL.x, bottomL.z);
	const auto& topL = P(bottomL.x, bottomL.z - RectH);
	const auto& topR = P(-bottomL.x, topL.z);
	int lastSize = vertices.size();
	vertices << topL << bottomL << bottomR << topR;
	indices << lastSize + 0 << lastSize + 1 << lastSize + 2 << lastSize + 0 << lastSize + 2 << lastSize + 3;

	addUniform("bottomLeft", Vec3(-W/2, Config::Y_AccTG, -OffsetD));
	addUniform("maxHeight", 4 * RectH + 3 * Interval);
}

/** 
 * @note child entity set some member variables, not means that it's parent entity has set the same value!
 */
void MarkerEntity::createTailLampLight(const CColor& color, HDir hdir, bool asChild) {
	CHECK_ZHICHE_INFO_AVAILABILITY
	auto e = this;
	if (asChild) e = new MarkerEntity(this);

	const auto& Info = GLTFParser::zhicheInfo();
	const auto X = Info.maxX * 0.75 * (HDir::LEFT == hdir ? -1 : 1);
	const auto Y = Info.height * 0.25;
	const auto Z = Info.maxZ;
	auto Center = Vec3(X, Y, Z);
	const auto HRadius = Info.width * Config::Marker_Lamp_LightRadiusByModelWTimes;
	const auto VRadius = HRadius * 1.0;
	CVector<Vec3> vertices;
	GeoUtil::createEllipseXY(vertices, Center, 2 * HRadius, 2 * VRadius, 72);

	e->addUniformStruct("lamp",
		Field("center", Center),
		Field("hRadius", HRadius),
		Field("vRadius", VRadius)
	);

	e->install(Config::Shader_LampLight, PrimitiveType::TRIANGLE_FAN, vertices, color);
}

void MarkerEntity::createLineEllipse(const LineEq& eq, const CColor& color, HDir hdir) {
	auto e = new MarkerEntity(this);

	CVector<P> vertices;
	GeoUtil::createLineEllipse(vertices, eq, Config::Marker_Ellipse_CenterZ, Config::Marker_Ellipse_HAxis, Config::Marker_Ellipse_VAxis, hdir);

	e->addUniformStruct("ellipse",
		Field("centerZ", Config::Marker_Ellipse_CenterZ),
		Field("hAxis", Config::Marker_Ellipse_HAxis),
		Field("vAxis", Config::Marker_Ellipse_VAxis)
	);

	e->addUniformArray("eqC", eq.C);

	e->install(Config::Shader_LineEllipse, PrimitiveType::TRIANGLE_STRIP, vertices, Config::Y_Ellipse, color);
}

void MarkerEntity::createSQ_ALC(RenderOrder order) {
	m_renderOrder = order;
	CHECK_ZHICHE_INFO_AVAILABILITY
	sq::ALC alc = Cache::sqStatus().alc;
	LineEq eqL, eqR;
	switch (alc) {
		case sq::ALC::Left:
			eqL = Cache::lineArg(LineTag::L2).eq;
			eqR = Cache::lineArg(LineTag::L).eq;
			break;
		case sq::ALC::Right:
			eqL = Cache::lineArg(LineTag::R).eq;
			eqR = Cache::lineArg(LineTag::R2).eq;
			break;
	}
	if (!eqL.isValid() || !eqR.isValid())
		return;

	const auto& ZhicheInfo = GLTFParser::zhicheInfo();
	const float CenterZ = ZhicheInfo.maxZ - ZhicheInfo.length / 2;
	const float RectWidthRatio = 0.8;
	auto lCx = GeoUtil::getXByEquation(eqL, CenterZ);
	auto rCx = GeoUtil::getXByEquation(eqR, CenterZ);

	auto rectCenter = P((lCx + rCx) / 2, CenterZ);
	auto rectWidth = abs(lCx - rCx) * RectWidthRatio;

	const float RectLength = ZhicheInfo.length;
	const float DashBold = Config::Line_Width * 0.8f;
	const float DashLen = DashBold * 4;

	CVector<P> rectVertices;
	CVector<UInt> rectIndices;
	GeoUtil::createDashedRect(rectVertices, rectIndices, rectCenter, rectWidth, RectLength, DashLen, DashBold);

	auto e = install(Config::Shader_AnyFree, PrimitiveType::TRIANGLES, rectVertices, Config::Y_Line, rectIndices, "white", ASCHILD);
	if (!e) return;

	CVector<P> bgVertices;
	GeoUtil::createFilledLane(bgVertices, eqL, eqR);
	CColor color = Config::Marker_DclcArrow_Color;
	color.setAlpha(0.4);

	e = install(Config::Shader_LaneBg, PrimitiveType::TRIANGLE_STRIP, bgVertices, Config::Y_Ellipse, color, ASCHILD);
	if (!e) return;

	e->addUniformStruct("range",
		Field("zStart", bgVertices.front().z),
		Field("zEnd", bgVertices.back().z),
		Field("zCenter", CenterZ)
	);
}

Entity* MarkerEntity::install(const CString& shaderName, PrimitiveType primitiveType, const CVector<P>& vertices, float vertex2dY, const CColor& color, bool asChild) {
	return install(shaderName, primitiveType, vertices, vertex2dY, {}, color, asChild);
}

Entity* MarkerEntity::install(const CString& shaderName, PrimitiveType primitiveType, const CVector<P>& vertices, float vertex2dY, const CVector<UInt>& indices, const CColor& color, bool asChild) {
	return toInstall(shaderName, primitiveType, vertices, vertex2dY, {}, indices, color, asChild);
}

Entity* MarkerEntity::install(const CString& shaderName, PrimitiveType primitiveType, const CVector<Vec3>& vertices, const CColor& color, bool asChild) {
	return install(shaderName, primitiveType, vertices, {}, color, asChild);
}

Entity* MarkerEntity::install(const CString& shaderName, PrimitiveType primitiveType, const CVector<Vec3>& vertices, const CVector<UInt>& indices, const CColor& color, bool asChild) {
	return toInstall(shaderName, primitiveType, {}, -1, vertices, indices, color, asChild);
}

Entity* MarkerEntity::toInstall(const CString& shaderName, PrimitiveType primitiveType, const CVector<P>& vertices2d, float vertex2dY, const CVector<Vec3>& vertices3d, const CVector<UInt>& indices, const CColor& color, bool asChild) {
	if (vertices2d.empty() && vertices3d.empty())
		return nullptr;

	auto e = this;
	if (asChild) e = new MarkerEntity(this);

	if (!vertices2d.empty())
		e->setVertices(vertices2d, vertex2dY);
	else
		e->setVertices(vertices3d);
	e->setIndices(indices);
	e->setPureColor(color);
	e->setPrimitiveType(primitiveType);

	e->Entity::install(shaderName);
	return e;
}